/*
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package hextd.core.gameloop;

import hextd.HexTD;
import hextd.HexTD.GameState;
import hextd.blackBox.BlackBox;
import hextd.core.Context;
import hextd.core.ContextNormal;
import hextd.core.gameloop.GameLoopImplementation.GliChecking;
import hextd.core.gameloop.GameLoopImplementation.GliNull;
import hextd.core.gameloop.GameLoopImplementation.GliReplaying;
import hextd.core.gameloop.GameLoopImplementation.GliSpPlaying;
import java.util.Date;

/**
 *
 * @author hylke
 */
public class GameLoopManager implements Runnable {

	private Thread gameLoopThread;
	private Context context;
	private ContextNormal cn;
	private BlackBox blackBoxPlayback;
	private BlackBox blackBoxRecord;
	private final Context.GameTime gameTime;
	//
	private boolean running = false;
	private boolean paused = false;
	private boolean ff = false;
	private int pauseAtTick = -1;
	private int tickTime = 50;
	private int tickTimeSkip = 0;
	private int tickTimeFast = 15;
	private int tickTimeNormal = 50;
	// the loops that have graceTicks
	private int graceTicksMax = 5;
	private GameLoopImplementation.GliSpPlaying gliSpPlaying;
	private GameLoopImplementation.GliReplaying gliSpReplaying;
	private GameLoopImplementation.GliChecking gliSpChecking;
	private static GliNull nullLoop;

	public GameLoopManager(Context c, BlackBox blackBoxPlayback, BlackBox blackBoxRecord) {
		context = c;
		if (c instanceof ContextNormal) {
			cn = (ContextNormal) c;
		}
		gameTime = c.getGameTime();
		this.blackBoxPlayback = blackBoxPlayback;
		this.blackBoxRecord = blackBoxRecord;
		doInit();
	}

	private void doInit() {
		nullLoop = new GameLoopImplementation.GliNull();
		for (GameState c : GameState.values()) {
			c.gameLoop = nullLoop;
		}
		// We take the loops that have graceticks apart
		gliSpPlaying = new GameLoopImplementation.GliSpPlaying(context, gameTime);
		gliSpReplaying = new GameLoopImplementation.GliReplaying(context, gameTime, blackBoxPlayback);
		gliSpChecking = new GameLoopImplementation.GliChecking(context, gameTime, blackBoxPlayback);
		GameState.spPlaying.gameLoop = gliSpPlaying;
		GameState.spPlayingWaveless.gameLoop = new GameLoopImplementation.GliSpPlayingWaveless(context, gameTime);
		GameState.replaying.gameLoop = gliSpReplaying;
		GameState.checking.gameLoop = gliSpChecking;
		GameState.mpPlaying.gameLoop = new GameLoopImplementation.GliMpPlaying(context, gameTime, blackBoxRecord);
		GameState.mpChecking.gameLoop = new GameLoopImplementation.GliMpChecking(context, gameTime, blackBoxPlayback);
		GameState.mpCatchup.gameLoop = new GameLoopImplementation.GliMpCatchup(context, gameTime, blackBoxRecord);
		GameState.mpWatching.gameLoop = new GameLoopImplementation.GliMpWatching(context, gameTime, blackBoxRecord);
	}

	@Override
	public void run() {
		long oldTime, nowTime, sleepTime, sleepTimeSuggested;
		oldTime = new Date().getTime();
		running = true;
		System.out.println("GameLoop::run: GameLogicThread starting.");
		while (running) {
			sleepTimeSuggested = 0;
			if (!paused) {
				context.getGameState().gameLoop.execute();
			}

			if (gameTime.getTime() == pauseAtTick && cn != null && !paused) {
				tickTime = tickTimeNormal;
				cn.setPause(true, false);
			}

			nowTime = new Date().getTime();
			sleepTime = oldTime + tickTime + sleepTimeSuggested - nowTime;

			if (sleepTime < 0) {
				oldTime = nowTime;
				sleepTime = 0;
				sleepTimeSuggested = 0;
			}

			if (context.getGameResult() == HexTD.GameResult.ended) {
				sleepTime = 0;
				sleepTimeSuggested = 0;
				running = false;
			}

			oldTime = oldTime + tickTime + sleepTimeSuggested;

			try {
				Thread.sleep(sleepTime);
			} catch (InterruptedException ex) {
				// do nothing
			}
		}
		System.out.println("GameLoop::run: GameLogicThread stopping.");
	}

	public void startGameLoop() {
		if (!running) {
			System.out.println("GameLoop::startGameLoop: starting.");
			running = true;
			gameLoopThread = new Thread(this, "GameLoop");
			gameLoopThread.start();
		} else {
			System.out.println("HexTD::startGameLoop: already running.");
		}
	}

	public void stopGameLoop() {
		setRunning(false);
		pauseAtTick = -1;
	}

	public boolean isRunning() {
		return running;
	}

	public void setRunning(boolean r) {
		running = r;
	}

	public void setFastForward(boolean f) {
		ff = f;
		synchronized (gameTime) {
			if (ff) {
				tickTime = tickTimeFast;
			} else {
				tickTime = tickTimeNormal;
			}
		}
	}

	public boolean isFastForward() {
		return ff;
	}

	public void toggleGameSpeed() {
		setFastForward(!ff);
	}

	public void setMaxGameSpeed() {
		synchronized (gameTime) {
			tickTime = tickTimeSkip;
		}
	}

	public void endMaxGameSpeed() {
		synchronized (gameTime) {
			setFastForward(ff);
		}
	}

	public boolean isPaused() {
		return paused;
	}

	public void setPaused(boolean p) {
		paused = p;
	}

	public int getTickTimeFast() {
		return tickTimeFast;
	}

	public void setTickTimeFast(int ttf) {
		tickTimeFast = ttf;
		if (ff) {
			tickTime = tickTimeFast;
		}
	}

	public int getPauseAtTick() {
		return pauseAtTick;
	}

	public synchronized void setPauseAtTick(int pat) {
		pauseAtTick = pat;
	}

	public void resetGraceTicks() {
		gliSpPlaying.setGraceTicks(graceTicksMax);
		gliSpReplaying.setGraceTicks(graceTicksMax);
		gliSpChecking.setGraceTicks(graceTicksMax);
	}

	public void cleanup() {
		stopGameLoop();
		gameLoopThread = null;
	}
}
